/**
 * 实现项目中用到的一些琐碎功能代码：
 * 日志宏的封装
 * json的序列化和反序列化的封装
 * 数据库相关操作的封装
 * 字符串分割功能封装
 * 文件读取功能封装
*/
#pragma once
#include <memory>
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <vector>
#include <cstdint>
#include <json/json.h>
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
#include <mysql/mysql.h>
#include "Log.hpp"

using wsserver_t=websocketpp::server<websocketpp::config::asio>;

namespace Detail
{
// 定义日志宏常量简化传参
#define LOG(level, format, ...)                                                  \
    do                                                                           \
    {                                                                            \
        LogMessage(__FILE__, __LINE__, Save_File, level, format, ##__VA_ARGS__); \
    } while (0)
//"__FILE__" 能自动获取当前文件名字 ; "__LINE__" 能自动获取当前代码所在的行数
//"__VA_ARGS__" 处理可变参数的宏，但是当可变参数为空时，会出问题，一般使用"##__VA_ARGS__"

// 定义宏控制是否保存日志信息到日志文件中
#define SAVEFILE()        \
    do                    \
    {                     \
        Save_File = true; \
    } while (0)

#define SAVESCREEN()       \
    do                     \
    {                      \
        Save_File = false; \
    } while (0)

    // Json序列化/反序列化
    class JSON
    {
    public:
        static bool Serialize(const Json::Value& val, std::string &out) // json序列化
        {
            // 实例化一个工厂类对象
            Json::StreamWriterBuilder swb;
            // 通过工厂类对象来生成派生类对象
            std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());

            // 创建字符串流对象
            std::stringstream ss;
            int n = sw->write(val, &ss);
            if (n != 0)
            {
                LOG(ERROR, "Json Serialize failed!\n");
                return false;
            }
            out = ss.str();
            return true;
        }

        static bool UnSerialize(const std::string &str, Json::Value &val) // json反序列化
        {
            // 实例化一个工厂类对象
            Json::CharReaderBuilder crb;
            // 通过工厂类对象来生成派生类对象
            std::unique_ptr<Json::CharReader> cr(crb.newCharReader());

            std::string err; // 保存错误信息
            bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), &val, &err);
            if (!ret)
            {
                LOG(ERROR, "Json UnSerialize failed : %s\n", err.c_str());
            }
            return ret;
        }
    };

    //mysql相关操作
    class MySql
    {
    public:
        //创建MySql
        static MYSQL *MySql_Create(const std::string &host,
                                   const std::string &username,
                                   const std::string &password,
                                   const std::string &dbname,
                                   uint16_t port = 3306) 
        {
            // 1.初始化MYSQL句柄    
            MYSQL* mysql=mysql_init(NULL);
            if(mysql==NULL)
            {
                LOG(ERROR,"mysql_init failed\n");
                return NULL;
            }

            // 2.连接服务器
            if(mysql_real_connect(mysql,host.c_str(),username.c_str(),password.c_str()
                                  ,dbname.c_str(),port,nullptr,0)==NULL)
            {
                LOG(ERROR,"mysql_real_connect failed:%s\n",mysql_error(mysql));
                //mysql_close(mysql);
                return NULL;
            }

            // 3.设置字符集
            if(mysql_set_character_set(mysql,"utf8")!=0)
            {
                LOG(ERROR,"mysql_set_character_set failed:%s\n",mysql_error(mysql));
                //mysql_close(mysql);
                return NULL;
            }
            return mysql;
        }

        //执行sql语句
        static bool MySql_Exec(MYSQL* mysql,const std::string& sql)
        {
            int ret=mysql_query(mysql,sql.c_str());
            if(ret!=0)
            {
                LOG(ERROR,"%s\n",sql.c_str());
                LOG(ERROR,"mysql_query failed:%s\n",mysql_error(mysql));
                //mysql_close(mysql);
                return false;
            }
            return true;
        }

        //销毁
        static void MySql_Destroy(MYSQL *mysql)
        {
            if (mysql != NULL)
            {
                mysql_close(mysql);
            }
            return ;
        }
    };

    //字符串分割
    class StringSplit
    {
    public:
        //把src字符串按照sep分隔符分割后存进res中
        static int Split(const std::string& src,const std::string& sep,std::vector<std::string>& res)
        {
            size_t pos=0,index=0;
            while(index<src.size())
            {
                pos=src.find(sep,index);
                if(pos==std::string::npos)//找到最后一个
                {
                    res.emplace_back(src.substr(index));
                    break;
                }
                if(pos==index)//出现连续的分隔符
                {
                    index+=sep.size();
                    continue;
                }
                res.emplace_back(src.substr(index,pos-index));
                index=pos+sep.size();
            }
            return res.size();
        } 
    };

    //文件读取
    class ReadFile
    {
    public:
        //从filename文件中读取内容，放入body中
        static bool Read(const std::string& filename,std::string& body)
        {
            //以二进制形式打开文件
            std::ifstream in(filename,std::ios::binary);
            if(!in.is_open())
            {
                LOG(ERROR,"%s file open failed\n",filename.c_str());
                return false;
            }

            //获取文件大小
            size_t filesize=0;
            //设置文件读取位置为文件末尾
            in.seekg(0,in.end);//以文件末尾为基地址，偏移量为0
            filesize=in.tellg();//获取当前读取位置与文件起始位置的偏移量
            //设置文件读取位置为文件起始地址
            in.seekg(0,in.beg);//以文件起始地址为基地址，偏移量为0

            body.resize(filesize);

            //读取文件内容
            in.read(&body[0],filesize);//不能用body.c_str(),它返回的是一个const char*
            if(!in.good())//上一次操作出错，即文件读取出错
            {
                LOG(ERROR,"read %s file failed\n",filename.c_str());
                in.close();
                return false;
            }
            in.close();
            return true;
        }
    };
}
